#include "zlog.hh"
#include <memory>
#include <fstream>
#include <stdarg.h>
#include "spdlog/spdlog.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/stdout_sinks.h"
#include "spdlog/async.h"
#include "spdlog/pattern_formatter.h"
#include "zuclog.h"
#include "config.h"
#include "datacollector.hh"

using namespace std;
const size_t ZLOG_FILE_MAX_SIZE = 1024 * 1024 * 100;
const size_t ZLOG_FILE_MAX_ROTAT_CNT = 4;
const char* ZLOG_DEBUG_FILE = ZUC_LOG_DIR "/zucdbg.log";
const char* ZLOG_ERROR_FILE = ZUC_LOG_DIR "/errorlog";
const size_t ZLOG_LINE_BUF_SIZE = 2048;

class DbgLog
{
public:
    DbgLog() {}
    ~DbgLog()
    {
        logger->set_level(spdlog::level::off);
        logger->flush();
    }
    int init()
    {
        thread_pool = spdlog::thread_pool();
        auto console_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
        console_sink->set_level(spdlog::level::off);
        auto sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(ZLOG_DEBUG_FILE, ZLOG_FILE_MAX_SIZE, ZLOG_FILE_MAX_ROTAT_CNT);
        logger = std::make_shared<spdlog::async_logger>("jkdbglog", sink, thread_pool, spdlog::async_overflow_policy::discard_new);
        logger->set_level(spdlog::level::debug);
        logger->sinks().push_back(console_sink);
        logger->set_pattern("%L %Y\\%m\\%d %H:%M:%S:%f [%P:%t] %v");
        spdlog::register_logger(logger);
        return 0;
    }
    std::shared_ptr<spdlog::logger> logger;
    std::shared_ptr<spdlog::details::thread_pool> thread_pool;
};

class AppLogLevelFlag : public spdlog::custom_flag_formatter
{
public:
    void format(const spdlog::details::log_msg& msg, const std::tm&, spdlog::memory_buf_t& dest) override
    {
        std::string s = "0";
        switch (msg.level)
        {
        case SPDLOG_LEVEL_ERROR:
            s = "3";
            break;
        case SPDLOG_LEVEL_WARN:
            s = "2";
            break;
        case SPDLOG_LEVEL_INFO:
            s = "1";
            break;
        default:
            break;
        }
        dest.append(s.data(), s.data() + s.size());
    }

    std::unique_ptr<custom_flag_formatter> clone() const override { return spdlog::details::make_unique<AppLogLevelFlag>(); }
};
class AppLog
{
public:
    AppLog()
    {
        char buff[1024] = {0};
        int s = sprintf(buff, "\n******** Log start (%s) ********\n", get_time().c_str());
        ofstream f(ZLOG_ERROR_FILE, ios_base::app);
        f.write(buff, s);
        f.flush();
    }
    ~AppLog()
    {
        logger->set_level(spdlog::level::off);
        logger->flush();
        char buff[1024] = {0};
        int s = sprintf(buff, "\n******** Log end (%s) ********\n", get_time().c_str());
        ofstream f(ZLOG_ERROR_FILE, ios_base::app);
        f.write(buff, s);
        f.flush();
    }
    static string get_time()
    {
        time_t cur_t;
        struct tm cur_tm;
        time(&cur_t);
        localtime_r(&cur_t, &cur_tm);
        char cur_system_time[32] = {0};
        size_t s = strftime(cur_system_time, sizeof(cur_system_time), "%Y\\%m\\%d %T", &cur_tm);
        return string(cur_system_time);
    }
    int init()
    {
        thread_pool = spdlog::thread_pool();
        auto sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(ZLOG_ERROR_FILE, ZLOG_FILE_MAX_SIZE, ZLOG_FILE_MAX_ROTAT_CNT);
        logger = std::make_shared<spdlog::async_logger>("jkapplog", sink, thread_pool, spdlog::async_overflow_policy::discard_new);
        // logger->set_level(spdlog::level::info);
        logger->flush_on(spdlog::level::info);
        auto formatter = spdlog::details::make_unique<spdlog::pattern_formatter>();
        formatter->add_flag<AppLogLevelFlag>('*').set_pattern("%Y/%m/%d %H:%M:%S:%e # %* %v");
        logger->set_formatter(std::move(formatter));
        spdlog::register_logger(logger);

        return 0;
    }
    std::shared_ptr<spdlog::logger> logger;
    std::shared_ptr<spdlog::details::thread_pool> thread_pool;
};
std::shared_ptr<DbgLog> dbgLog;
std::shared_ptr<AppLog> appLog;

int zucLogInit()
{
    dbgLog = make_shared<DbgLog>();
    appLog = make_shared<AppLog>();
    spdlog::init_thread_pool(10240, 1);
    dbgLog->init();
    appLog->init();

    spdlog::flush_every(std::chrono::seconds(1));
    return 0;
}
static void removeTail(char* buf, int len)
{
    for (int i = len - 1; i >= 0; i--)
    {
        if (buf[i] == '\n' || buf[i] == '\t' || buf[i] == ' ')
        {
            buf[i] = 0;
        }
        else
        {
            break;
        }
    }
}
int __zucInnerDbg(unsigned char lv, unsigned int line, const char* filename, const char* fmt, ...)
{
    char buf[ZLOG_LINE_BUF_SIZE] = {0};
    va_list ap;
    va_start(ap, fmt);
    int s = vsnprintf(buf, ZLOG_LINE_BUF_SIZE - 1, fmt, ap);
    va_end(ap);
    removeTail(buf, s);
    if (strlen(buf) == 0)
        return 0;

    switch (lv)
    {
    case ZLOGLV_E:
        dbgLog->logger->error("{}", buf);
        break;
    case ZLOGLV_W:
        dbgLog->logger->warn("{}", buf);
        break;
    case ZLOGLV_I:
        dbgLog->logger->info("{}", buf);
        break;
    case ZLOGLV_D:
        dbgLog->logger->debug("{}", buf);
        break;
    case ZLOGLV_T:
        dbgLog->logger->trace("{}", buf);
        break;
    default:
        break;
    }
    return 0;
}
int zucLogError(unsigned int code, const char* fmt, ...)
{
    char buf[ZLOG_LINE_BUF_SIZE] = {0};
    va_list ap;
    va_start(ap, fmt);
    int s = vsnprintf(buf, ZLOG_LINE_BUF_SIZE - 1, fmt, ap);
    va_end(ap);
    removeTail(buf, s);
    appLog->logger->error("# {} # {}", code, buf);
    zucDiagEcode(code);
    return 0;
}
int zucLogWarning(unsigned int code, const char* fmt, ...)
{
    char buf[ZLOG_LINE_BUF_SIZE] = {0};
    va_list ap;
    va_start(ap, fmt);
    int s = vsnprintf(buf, ZLOG_LINE_BUF_SIZE - 1, fmt, ap);
    va_end(ap);
    removeTail(buf, s);
    appLog->logger->warn("# {} # {}", code, buf);
    zucDiagEcode(code);
    return 0;
}
int zucLogInfo(unsigned int code, const char* fmt, ...)
{
    char buf[ZLOG_LINE_BUF_SIZE] = {0};
    va_list ap;
    va_start(ap, fmt);
    int s = vsnprintf(buf, ZLOG_LINE_BUF_SIZE - 1, fmt, ap);
    va_end(ap);
    removeTail(buf, s);
    appLog->logger->info("# {} # {}", code, buf);
    zucDiagEcode(code);
    return 0;
}
int zucSetLogLevel(unsigned char logLevel)
{
    spdlog::level::level_enum l = spdlog::level::info;
    switch (logLevel)
    {
    case ZLOGLV_E:
        l = spdlog::level::err;
        break;
    case ZLOGLV_W:
        l = spdlog::level::warn;
        break;
    case ZLOGLV_I:
        l = spdlog::level::info;
        break;
    case ZLOGLV_D:
        l = spdlog::level::debug;
        break;
    case ZLOGLV_T:
        l = spdlog::level::trace;
        break;
    default:
        l = spdlog::level::n_levels;
        break;
    }
    if (dbgLog->logger->sinks()[1]->level() == spdlog::level::off)
    {
        dbgLog->logger->sinks()[0]->set_level(l);
    }
    else
    {
        dbgLog->logger->set_level(l);
    }

    return 0;
}
int zucEnableStdout(unsigned char en)
{
    if (en)
    {
        dbgLog->logger->sinks()[1]->set_level(dbgLog->logger->sinks()[0]->level());
    }
    else
    {
        dbgLog->logger->sinks()[1]->set_level(spdlog::level::off);
    }
    return 0;
}

std::shared_ptr<spdlog::logger> zlog() { return dbgLog->logger; }